<?php

namespace Api\Sms;

use Api\RepositoryResourceTrait;
use Carbon\Carbon;
use Log;

/**
 * 资源仓库
 */
class SmsRepository
{
    use RepositoryResourceTrait;

    /**
     * 构造方法
     */
    public function __construct(Models\SmsVerificationCode $Model, array $config)
    {
        $this->Models = compact('Model');
        $this->config = $config;
    }

    /**
     * 发送短信验证码
     *
     * @param  string $code        验证码
     * @param  string $countryCode 国际区号
     * @param  string $mobile      手机号码
     * @return array  [$smsStatus, $smsCallbackMessage]
     */
    protected function _sendVerificationCode(string $code, string $countryCode, string $mobile)
    {
        d('need _sendVerificationCode method.');
    }

    /**
     * 发送验证码，并存储 校验字符串
     *
     * @param  string $type          验证码类型
     * @param  string $mobile        手机号码
     * @param  string $countryCode   国际区号
     * @param  int    $confineSecond 有效期（秒）
     * @return void
     */
    public function sendVerificationCode(string $type, string $mobile, string $countryCode = '86', int $confineSecond = null)
    {
        $model = $this->Model;
        $code = mt_rand(111111, 999999);
        $hashCode = app('hash')->make($type . $code . $mobile);
        $confineSecond = $confineSecond ?: $this->config['verificationCodeConfine'];

        // 发起请求的 IP（防止伪造手机号，暴力消耗验证码）
        $requestIp = app('request')->ip();

        // 发送验证码短信
        if ($this->config['debug']) {
            Log::info($mobile . ':' . $code);
        } else {
            // 确认数据库有值
            $smsInfo = $model
                ->where('type', $type)
                ->where('mobile', $mobile)
                ->where('country_code', $countryCode)
                ->orderBy('confine_time', 'desc')
                ->first();

            // 上次请求的验证码尚未过期
            if ($smsInfo && $smsInfo->created_at->addSecond(60)->gt(Carbon::now())) {
                // 时间差
                $confineSecond = $smsInfo->created_at->addSecond(60)->diffInSeconds();
                throw_exception('Sms', 'TheLastRequestHasNotExpired', trans('sms.codeFrequent', ['confineSecond' => $confineSecond]));
            }

            // 单IP请求的验证码数量超出限制（防止伪造手机号，暴力消耗验证码）
            $smsSendCount = $model->where('mobile', $mobile)->where('request_ip', $requestIp)->count();
            if ($smsSendCount > 100) {
                throw_exception('Sms', 'OverLimit', trans('sms.operaField'), '[countryCode:' . $countryCode . ', mobile:' . $mobile . '] 单IP [' . $requestIp . '] The number of authentication codes requested exceeds the limit.');
            }
            
            // 真实发送
            list($smsStatus, $smsCallbackMessage) = $this->_sendVerificationCode($code, $countryCode, $mobile);
            if ($smsStatus === false) {
                throw_exception('Sms', 'SendFail', $smsCallbackMessage, '[countryCode:' . $countryCode . ', mobile:' . $mobile . '] ' . $smsCallbackMessage);
            }
        }
        // 删除旧数据
        $model->where('country_code', $countryCode)->where('mobile', $mobile)->where('type', $type)->delete();

        // 存储验证信息
        $model->code = $code;
        $model->type = $type;
        $model->mobile = $mobile;
        $model->country_code = $countryCode;
        $model->hash = $hashCode;
        $model->request_ip = $requestIp;
        $model->confine_time = Carbon::now()->addSecond($confineSecond);
        $model->saveOrFail();
    }

    /**
     * 校验验证码
     *
     * @param  string $inputCode   用户输入的验证码
     * @param  string $type        验证码类型
     * @param  string $mobile      手机号码
     * @param  string $countryCode 国际区号
     * @return void
     */
    public function checkVerificationCode(string $inputCode = null, string $type, string $mobile, string $countryCode = '86')
    {
        if (empty($inputCode)) {
            throw_exception('Sms', 'VerificationCodeEmpty', trans('sms.verificationEmpty'));
        }
        // 确认数据库有值
        $model = $this->Model;
        $smsInfo = $model
            ->where('type', $type)
            ->where('mobile', $mobile)
            ->where('country_code', $countryCode)
            ->orderBy('confine_time', 'desc')
            ->first();

        // 数据库无对应凭证
        if (is_null($smsInfo)) {
            throw_exception('Sms', 'TheDatabaseHasNoCorrespondingCredentials', trans('sms.VerificationField'), '[countryCode:' . $countryCode . ', mobile:' . $mobile . '] Database has no corresponding credentials.');
        }

        // // 与获取验证码的 IP 不匹配
        // if ($sms->request_ip !== app('request')->ip()) {
        //     throw_exception('Sms', 'IPAddressMismatch', '验证码错误', '[countryCode:'.$countryCode.', mobile:'.$mobile.'] 与获取验证码的 IP 不匹配');
        // }

        // 验证码过期
        if ($smsInfo->confine_time < Carbon::now()) {
            throw_exception('Sms', 'VerificationCodeExpiration', 'Verification code expires');
        }

        // 哈希校验失败
        if ($smsInfo->hash_fail || !app('hash')->check($type . $inputCode . $mobile, $smsInfo->hash)) {
            throw_exception('Sms', 'HashCheckFailed', trans('sms.VerificationField'), '[countryCode:' . $countryCode . ', mobile:' . $mobile . '] Hash check failed');
        }

        // 校验成功废弃验证码
        $model->where('hash', $smsInfo->hash)->update(['hash_fail' => true]);
    }

}
